"""
Implementation of a very simple set of classes for representing objects in a
given scene. Most of the attributes and properties model OpenGL primitives and
state variables, so that their meaning is mostly self-documenting. The idea here
is that objects may be passed around or maybe stored (using pickle or something
like that). Rendering an object should be as simple as calling its draw method.
"""

#__all__=["EditableVisualElement","GraphicObject","GraphicObjectCollection"]
__all__=["GraphicObject","GraphicObjectCollection"]

from OpenGL.GL import *
from OpenGL.GLU import *

from material import *
import matrix
#from bbox import *
from math import sqrt
#from EditableVisualElement import *

# -*- coding: utf-8 -*-
## This code is responsable for creating visual element structures to support other 3DbyStep routines.
## dispose a group of interations inside these environment.
## Master Research:         Elisabete Thomaselli Nogueira
## Professor:               Claudio Esperanca
## Graduate student:        Victor Soares Bursztyn
## Created in: 07-11-07
## Updated in:  ---
## Last Update: ---

## Class EditableVisualElement defines all transformations and properties (rotation, translation, scale, color, etc) related to visual Elements.

from numpy import *
from math import sin, cos

class EditableVisualElement(object):

    tx, ty, tz = 0, 0, 0
    rx, ry, rz = 0, 0, 0
    sx, sy, sz = 1.0, 1.0, 1.0
    r = 0.3
    g = 0.6
    b = 0.8
    #color = QtGui.QColor(r, g, b)

    def setRotate(self, x, y, z):

        self.rx, self.ry, self.rz = float(x), float(y), float(z)

    def setTranslate(self, x, y, z):

        self.tx, self.ty, self.tz = float(x), float(y), float(z)

    def setScale(self, x, y, z):

        self.sx, self.sy, self.sz = float(x), float(y), float(z)

    def setColor(self, r, g, b):

        self.r, self.g, self.b = float(r/255), float(g/255), float(b/255)

    def setListAnimations(self, animList):
        self.animList = animList

    def setAnimation(self,  animType,  playerExit, initFrame,  lastFrame,  vel, delayTime):
        self.animType = animType
        self.playerExit = playerExit
        self.initFrame = initFrame
        self.lastFrame = lastFrame
        self.vel = vel
        self.delayTime = delayTime      ## Talvez nao se aplique

    def getTransformation(self):

        transformation = []

        transformation.append(self.tx)
        transformation.append(self.ty)
        transformation.append(self.tz)
        transformation.append(self.rx)
        transformation.append(self.ry)
        transformation.append(self.rz)
        transformation.append(self.sx)
        transformation.append(self.sy)
        transformation.append(self.sz)
        #print "teste transformation", tx, ty, tz
        return transformation

    def setTransformationMatrix2(self, g_Transform):
        self.transformationMatrix = g_Transform

    def getTransformationMatrix2(self):

        transformationMatrix = []

        transformationMatrix.append(self.transformationMatrix)
        return transformationMatrix

    def getTransformationMatrix(self):

        sinx = sin(self.rx)
        siny = sin(self.ry)
        sinz = sin(self.rz)
        cosx = cos(self.rx)
        cosy = cos(self.ry)
        cosz = cos(self.rz)
        #ScTrMatrix = array([[self.sx, 0, 0, self.tx],[0, self.sy, 0, self.ty],[0, 0, self.sz, self.tz],[0,0,0,1]])
        #RotMatrix = array([[cosy*cosz, cosy*sinz, -siny, 0],[cosz*sinx*siny - cosx*sinz, cosx*cosz + sinx*siny*sinz, cosy*sinx, 0],[cosx*cosz*siny + sinx*sinz, -cosz*sinx + cosx*siny*sinz, cosx*cosy, 0],[0,0,0,1]])
        TMatrix = array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[self.tx,self.ty,self.tz,1]])
        SMatrix = array([[self.sx,0,0,0],[0,self.sy,0,0],[0,0,self.sz,0],[0,0,0,1]])
        RMatrix = array([[cosy*cosz, -cosy*sinz, siny, 0],[sinx*siny*cosz + cosx*sinz,
    -sinx*siny*sinz + cosx*cosz, -sinx*cosy, 0],[-cosx*siny*cosz + sinx*sinz, cosx*siny*sinz + sinx*cosz, cosx*cosy, 0],[0,0,0,1]])
        #RotMatrix = array([[cosy*cosz, cosz*sinx*siny - cosx*sinz, cosx*cosz*siny + sinx*sinz, 0],[cosy*sinz, cosx*cosz + sinx*siny*sinz, -cosz*sinx + cosx*siny*sinz, 0],[-siny, cosy*sinx, cosx*cosy, 0],[0,0,0,1]])
        tempMatrix = dot(RMatrix,SMatrix)
        print "%r", TMatrix
        print "%r", SMatrix
        print "%r", RMatrix
        print "%r", tempMatrix
        transformationMatrix = dot(TMatrix,tempMatrix)
        print "%r", transformationMatrix
        return transformationMatrix

    def getRGB(self):

        RGB = []

        RGB.append(self.r)
        RGB.append(self.g)
        RGB.append(self.b)
        return RGB

    def getCenter(self):

        return self.center + (self.tx, self.ty, self.tz)

    def getSide(self):

        return self.side * (self.sx, self.sy, self.sz)

    def __gettransform (self,func,*args):
        """Finds the transformation obtained by composing
        the object's current transformation with the call to some
        function func which affects the current modelview matrix.
        The resulting transformation is stored in self.transformation.

        @param func: a function which affects the current modelview matrix
        (usually glTranslate, glRotate or some such).
        @param args: arguments to be passed to func.
        @return: None.
        """
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        func(*args)
        if self._transformation is not None:
            glMultMatrixd(self._transformation)
        self.transformation = glGetDoublev(GL_MODELVIEW_MATRIX)
        glPopMatrix()



class GraphicObject(object):
    "Base class for graphic objects."

    tx, ty, tz = 0, 0, 0
    rx, ry, rz = 0, 0, 0
    sx, sy, sz = 1.0, 1.0, 1.0
    r = 0.3
    g = 0.6
    b = 0.8
    #color = QtGui.QColor(r, g, b)

    def __init__(self,box,material=None,transformation=None):
        """Constructor.
        @param box: A bounding box for this object.
        @param material: the material object (Material). If None, means that
        no material is attached to this object and thus it will be rendered
        with the current material.
        @param transformation: a transformation (16-float vector). If None,
        then an identity transformation is meant.
        """
        self._box = box
        self.material = material
        self.transformation = transformation

    def _changed (self):
        """Hook for doing bookkeeping whenever a property of the object
        is modified. Derived classes should rewrite this method for its own
        purposes"""
        pass

    def _settransf (self, transformation):
        "Setter for transformation"
        if transformation is None:
            self._transformation = matrix.identity(4)
        else:
            self._transformation = transformation
        self._changed ()

    def _gettransf (self):
        "Getter for transformation"
        return self._transformation

    def _setgrouptransf (self, grouptransformation):
        "Setter for groupo transformation"
        if grouptransformation is None:
            self._grouptransformation = matrix.identity(4)
        else:
            self._grouptransformation = grouptransformation
        self._changed ()

    def _getgrouptransf (self):
        "Getter for group transformation"
        return self._grouptransformation

    def _getbox(self):
        "Getter for box"
        return self._box

    def _setbox(self,box):
        "Setter for box"
        self._box = box
        self._changed()

    transformation = property (_gettransf, _settransf)
    ##?? grouptransformation = property (_getgrouptransf, _setgrouptransf)
    grouptransformation = property (_gettransf, _settransf)

    box = property (_getbox, _setbox)

    def setRotate(self, x, y, z):

        self.rx, self.ry, self.rz = float(x), float(y), float(z)

    def setTranslate(self, x, y, z):

        self.tx, self.ty, self.tz = float(x), float(y), float(z)

    def setScale(self, x, y, z):

        self.sx, self.sy, self.sz = float(x), float(y), float(z)

    def setColor(self, r, g, b):

        self.r, self.g, self.b = float(r/255), float(g/255), float(b/255)

    def position(self):
        """Returns the translation part of this object's transformation.
        @return: position vector."""
        if self._transformation is not None:
            return self._transformation[3][:3]
        return [0,0,0]


    def __gettransform (self,func,*args):
        """Finds the transformation obtained by composing
        the object's current transformation with the call to some
        function func which affects the current modelview matrix.
        The resulting transformation is stored in self.transformation.

        @param func: a function which affects the current modelview matrix
        (usually glTranslate, glRotate or some such).
        @param args: arguments to be passed to func.
        @return: None.
        """
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        func(*args)
        if self._transformation is not None:
            glMultMatrixd(self._transformation)
        self.transformation = glGetDoublev(GL_MODELVIEW_MATRIX)
        glPopMatrix()

    def rotate (self,angle,x,y,z):
        """Alters the object's transformation with the given rotation.
        @param angle: rotation angle in degrees.
        @param x,y,z: rotation axis.
        @return: None.
        """
        self.__gettransform (glRotatef,angle,x,y,z)

    def translate (self,dx,dy,dz):
        """Alters the object's transformation with the given translation.
        @param dx,dy,dz: offset vector.
        @return: None.
        """
        self.__gettransform (glTranslatef,dx,dy,dz)

    ##? 2008-05-05
    def grouptranslate (self,dx,dy,dz):
        """Alters the object's transformation with the given translation.
        @param dx,dy,dz: offset vector.
        @return: None.
        """
        self.__getgrouptransform (glTranslatef,dx,dy,dz)

    def scale (self,dx,dy,dz):
        """Alters the object's transformation with the given scale.
        @param dx,dy,dz: scale factors.
        @return: None.
        """
        self.__gettransform (glScalef,dx,dy,dz)

    def draw(self):
        """Object's drawing code."""
        if self.material:
            self.material.draw()
        if self._transformation is not None:
            glMultMatrixd(self._transformation)

class GraphicObjectCollection(GraphicObject,list):
    """A Collection of Graphic Objects."""

    def __init__(self, collection=[], material=None, transformation=None):
        """Constructor.
        @param material: the material object (Material). If None, means that
          no material is attached to this object and thus it will be rendered
          with the current material.
        @param transformation: a 4x4 matrix stored by column,
          i.e., according to the opengl standard. If None,
          then an identity transformation is meant.
        @param collection: a list of GraphicObjects or nothing at all.
        """
        GraphicObject.__init__(self,BoundingBox(),material,transformation)
        list.__init__(self,collection)
        self.computeBoundingBox()

    def computeBoundingBox(self):
        """Recomputes this collection's bounding box. Should be called
        before using the bounding box if it is not certain whether any of the
        elements' boxes have been modified.
        """
        allPoints = []
        for obj in self:
            if isinstance(obj,GraphicObject):
                allPoints += map(lambda p: matrix.gltransformpoint (obj.transformation, p),
                                 obj.box.vertices())
        self.box = BoundingBox.fromPoints(allPoints)

    def draw(self):
        """Draws every object in the list suitably preceded by a name (in the
        OpenGL sense) which is index of the object in the list so as to
        help during GL_SELECTION rendering mode."""
        GraphicObject.draw(self)
        for i in range(len(self)):
            glPushMatrix()
            glLoadName (i)
            self[i].draw()
            glPopMatrix()

    """
    Tinha pick e
    pickClosest aqui tambem
    """
    def setRotate(self, x, y, z):

        self.rx, self.ry, self.rz = float(x), float(y), float(z)

    def setTranslate(self, x, y, z):

        self.tx, self.ty, self.tz = float(x), float(y), float(z)

    def setScale(self, x, y, z):

        self.sx, self.sy, self.sz = float(x), float(y), float(z)

    def setColor(self, r, g, b):

        self.r, self.g, self.b = float(r/255), float(g/255), float(b/255)

    def setListAnimations(self, animList):
        self.animList = animList

    def setAnimation(self,  animType,  playerExit, initFrame,  lastFrame,  vel, delayTime):
        self.animType = animType
        self.playerExit = playerExit
        self.initFrame = initFrame
        self.lastFrame = lastFrame
        self.vel = vel

    def getTransformation_lx(self):

        transformation = []

        transformation.append(self.tx)
        transformation.append(self.ty)
        transformation.append(self.tz)
        transformation.append(self.rx)
        transformation.append(self.ry)
        transformation.append(self.rz)
        transformation.append(self.sx)
        transformation.append(self.sy)
        transformation.append(self.sz)
        #print "teste transformation", tx, ty, tz
        return transformation

    def setTransformationMatrix2(self, g_Transform):
        self.transformationMatrix = g_Transform

    def getTransformationMatrix2(self):

        transformationMatrix = []

        transformationMatrix.append(self.transformationMatrix)
        return transformationMatrix

    def getTransformationMatrix(self):

        sinx = sin(self.rx)
        siny = sin(self.ry)
        sinz = sin(self.rz)
        cosx = cos(self.rx)
        cosy = cos(self.ry)
        cosz = cos(self.rz)
        #ScTrMatrix = array([[self.sx, 0, 0, self.tx],[0, self.sy, 0, self.ty],[0, 0, self.sz, self.tz],[0,0,0,1]])
        #RotMatrix = array([[cosy*cosz, cosy*sinz, -siny, 0],[cosz*sinx*siny - cosx*sinz, cosx*cosz + sinx*siny*sinz, cosy*sinx, 0],[cosx*cosz*siny + sinx*sinz, -cosz*sinx + cosx*siny*sinz, cosx*cosy, 0],[0,0,0,1]])
        TMatrix = array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[self.tx,self.ty,self.tz,1]])
        SMatrix = array([[self.sx,0,0,0],[0,self.sy,0,0],[0,0,self.sz,0],[0,0,0,1]])
        RMatrix = array([[cosy*cosz, -cosy*sinz, siny, 0],[sinx*siny*cosz + cosx*sinz,
    -sinx*siny*sinz + cosx*cosz, -sinx*cosy, 0],[-cosx*siny*cosz + sinx*sinz, cosx*siny*sinz + sinx*cosz, cosx*cosy, 0],[0,0,0,1]])
        #RotMatrix = array([[cosy*cosz, cosz*sinx*siny - cosx*sinz, cosx*cosz*siny + sinx*sinz, 0],[cosy*sinz, cosx*cosz + sinx*siny*sinz, -cosz*sinx + cosx*siny*sinz, 0],[-siny, cosy*sinx, cosx*cosy, 0],[0,0,0,1]])
        tempMatrix = dot(RMatrix,SMatrix)
        print "%r", TMatrix
        print "%r", SMatrix
        print "%r", RMatrix
        print "%r", tempMatrix
        transformationMatrix = dot(TMatrix,tempMatrix)
        print "%r", transformationMatrix
        return transformationMatrix

    def getRGB(self):

        RGB = []

        RGB.append(self.r)
        RGB.append(self.g)
        RGB.append(self.b)
        return RGB

    def getCenter(self):

        return self.center + (self.tx, self.ty, self.tz)

    def getSide(self):

        return self.side * (self.sx, self.sy, self.sz)

    def __gettransform (self,func,*args):
        """Finds the transformation obtained by composing
        the object's current transformation with the call to some
        function func which affects the current modelview matrix.
        The resulting transformation is stored in self.transformation.

        @param func: a function which affects the current modelview matrix
        (usually glTranslate, glRotate or some such).
        @param args: arguments to be passed to func.
        @return: None.
        """
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        func(*args)
        if self._transformation is not None:
            glMultMatrixd(self._transformation)
        self.transformation = glGetDoublev(GL_MODELVIEW_MATRIX)
        glPopMatrix()

class GraphicObject_Esperanca(object):
    "Base class for graphic objects."

    def __init__(self,box,material=None,transformation=None):
        """Constructor.
        @param box: A bounding box for this object.
        @param material: the material object (Material). If None, means that
        no material is attached to this object and thus it will be rendered
        with the current material.
        @param transformation: a transformation (16-float vector). If None,
        then an identity transformation is meant.
        """
        self._box = box
        self.material = material
        self.transformation = transformation

    def _changed (self):
        """Hook for doing bookkeeping whenever a property of the object
        is modified. Derived classes should rewrite this method for its own
        purposes"""
        pass

    def _settransf (self, transformation):
        "Setter for transformation"
        if transformation is None:
            self._transformation = matrix.identity(4)
        else:
            self._transformation = transformation
        self._changed ()

    def _gettransf (self):
        "Getter for transformation"
        return self._transformation

    def _getbox(self):
        "Getter for box"
        return self._box

    def _setbox(self,box):
        "Setter for box"
        self._box = box
        self._changed()

    transformation = property (_gettransf, _settransf)
    box = property (_getbox, _setbox)

    def position(self):
        """Returns the translation part of this object's transformation.
        @return: position vector."""
        if self._transformation is not None:
            return self._transformation[3][:3]
        return [0,0,0]

    def __gettransform (self,func,*args):
        """Finds the transformation obtained by composing
        the object's current transformation with the call to some
        function func which affects the current modelview matrix.
        The resulting transformation is stored in self.transformation.

        @param func: a function which affects the current modelview matrix
        (usually glTranslate, glRotate or some such).
        @param args: arguments to be passed to func.
        @return: None.
        """
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        func(*args)
        if self._transformation is not None:
            glMultMatrixd(self._transformation)
        self.transformation = glGetDoublev(GL_MODELVIEW_MATRIX)
        glPopMatrix()

    def rotate (self,angle,x,y,z):
        """Alters the object's transformation with the given rotation.
        @param angle: rotation angle in degrees.
        @param x,y,z: rotation axis.
        @return: None.
        """
        self.__gettransform (glRotatef,angle,x,y,z)

    def translate (self,dx,dy,dz):
        """Alters the object's transformation with the given translation.
        @param dx,dy,dz: offset vector.
        @return: None.
        """
        self.__gettransform (glTranslatef,dx,dy,dz)

    def scale (self,dx,dy,dz):
        """Alters the object's transformation with the given scale.
        @param dx,dy,dz: scale factors.
        @return: None.
        """
        self.__gettransform (glScalef,dx,dy,dz)

    def draw(self):
        """Object's drawing code."""
        if self.material:
            self.material.draw()
        if self._transformation is not None:
            glMultMatrixd(self._transformation)

class GraphicObjectCollection(GraphicObject,list):
    """A Collection of Graphic Objects."""

    def __init__(self, collection=[], material=None, transformation=None):
        """Constructor.
        @param material: the material object (Material). If None, means that
          no material is attached to this object and thus it will be rendered
          with the current material.
        @param transformation: a 4x4 matrix stored by column,
          i.e., according to the opengl standard. If None,
          then an identity transformation is meant.
        @param collection: a list of GraphicObjects or nothing at all.
        """
        print "collection ", collection
        GraphicObject.__init__(self,BoundingBox(),material,transformation)
        list.__init__(self,collection)
        self.computeBoundingBox()

    def computeBoundingBox(self):
        """Recomputes this collection's bounding box. Should be called
        before using the bounding box if it is not certain whether any of the
        elements' boxes have been modified.
        """
        allPoints = []
        for obj in self:
            if isinstance(obj,GraphicObject):
                allPoints += map(lambda p: matrix.gltransformpoint (obj.transformation, p),
                                 obj.box.vertices())
        self.box = BoundingBox.fromPoints(allPoints)

    def draw(self):
        """Draws every object in the list suitably preceded by a name (in the
        OpenGL sense) which is index of the object in the list so as to
        help during GL_SELECTION rendering mode."""

        GraphicObject.draw(self)
        for i in range(len(self)):
            glPushMatrix()
            glLoadName (i)
            print "GraphicObject.draw entrada ", self[i]
            self[i].draw()
            glPopMatrix()

    def pick(self,x,y):
        """Uses the OpenGL selection mechanism for figuring out which object
        lies at screen position (x,y).
        @param x,y: screen position.
        @return: the selection buffer, i.e., a list of tuples (mindepth,
           maxdepth, names), where names is a list of object names -- i.e.,
           collection indices -- with the one on top of the stack last. Thus,
           if names==[3,10], then element 3 of this collection was hit
           and it was itself a collection whose element 10 was hit.
        """
        # Prepare for picking
        glSelectBuffer(100)
        glRenderMode(GL_SELECT)
        viewport = glGetIntegerv (GL_VIEWPORT)
        projmatrix = glGetDoublev (GL_PROJECTION_MATRIX)
        glInitNames()
        glPushName(0)
        glMatrixMode (GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity ()
        glMatrixMode (GL_PROJECTION)
        glPushMatrix ()
        glLoadIdentity ()
        # create 5x5 pixel picking region near cursor location
        gluPickMatrix (x, (viewport[3] - y), 5, 5, viewport);
        glMultMatrixd (projmatrix)

        #meu self.drawScene(True)
        self.draw()

        glPopName()
        buffer = glRenderMode(GL_RENDER)
        # Restore modelview
        glMatrixMode (GL_MODELVIEW)
        glPopMatrix ()
        # Restore projection
        glMatrixMode (GL_PROJECTION)
        glPopMatrix ()
        # Process hits
        return list(buffer)

    def pickClosest (self, x, y):
        """Uses the OpenGL selection mechanism for figuring out which object
        lies at screen position (x,y). Similar to pick, but returns only one
        object (the closest one).
        @param x,y: screen position.
        @return: A tuple (obj, z), where obj is the object and z is its z coordinate at
           the pick position. If no object lies at the given position, then
           obj is None."""
        closest = None
        closestZ = 1.0
        for hit in self.pick(x,y):
            minz, maxz, name = hit
            z = 1.0 * minz / (2**32-1)
            if z < closestZ:
                closestZ,closest = z, self[name[0]]
        return (closest, closestZ)


